﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Futurism.Universal.Foundation
{
    public class TypeContainer
    {
        private Dictionary<Type, Instance> registeredTypes = new Dictionary<Type, Instance>();

        public void Register(Type type, bool shared, Type baseType = null)
        {
            var typeKey = baseType ?? type;
            this.registeredTypes.Add(typeKey, shared ? new SharedInstance { Type = type, BaseType = baseType } : 
                new Instance { Type = type, BaseType = baseType });
        }

        public void Register<T>(bool shared)
            where T : class, new()
        {
            Register(typeof(T), shared);
        }

        public object Resolve(Type type)
        {
            var typeInfo = registeredTypes[type];

            if (typeInfo == null)
            {
                throw new KeyNotFoundException("The specified type is not registered.");
            }

            if (!(typeInfo is SharedInstance))
            {
                return CreateInstance(typeInfo.Type);
            }
            else
            {
                var sharedInstance = typeInfo as SharedInstance;

                if (sharedInstance.Value == null)
                {
                    sharedInstance.Value = CreateInstance(typeInfo.Type);
                }

                return sharedInstance.Value;
            }
        }

        public object Resolve(string typeName)
        {
            var registeredType = this.registeredTypes.Keys.Where(type => type.Name == typeName).First();
            return Resolve(registeredType);
        }

        public void Register<T>(T instance)
        {
            registeredTypes[typeof(T)] = new SharedInstance { Type = typeof(T), Value = instance };
        }

        public T Resolve<T>()
            where T : class, new()
        {
            return Resolve(typeof(T)) as T;
        }

        private object CreateInstance(Type type)
        {
            var ctors = type.GetTypeInfo().DeclaredConstructors;
            var importingCtor = ctors.Where(method => method.GetCustomAttribute<ImportAttribute>() != null).FirstOrDefault();
            var constructor = importingCtor ?? ctors.Where(ctor => !ctor.GetParameters().Any()).FirstOrDefault();
            
            if (constructor == null)
            {
                throw new Exception("Could not find either a constructor marked with 'Import' or a default one");
            }

            var ctorParameters = new List<object>();

            foreach (var dependency in constructor.GetParameters())
            {
                ctorParameters.Add(Resolve(dependency.ParameterType));
            }

            var instance = ctorParameters.Any() ? Activator.CreateInstance(type, ctorParameters.ToArray()) : Activator.CreateInstance(type);
            ///var instance = constructor.Invoke(null, ctorParameters.ToArray());
            
            foreach (var importedProperty in type.GetRuntimeProperties().Where(property => property.GetCustomAttribute<ImportAttribute>() != null))
            {
                var importAttribute = importedProperty.GetCustomAttribute<ImportAttribute>();
                var propertyType = importAttribute.ImportType ?? importedProperty.PropertyType;
                var propertyValue = Resolve(propertyType);
                importedProperty.SetValue(instance, propertyValue);
            }
            
            return instance;
        }
    }
}
